/*
* Copyright 2011 Alexandr Kalenuk.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
*   http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace LetterEnhancer
{
    class LeLoadException: FileNotFoundException{
        public LeLoadException(string message)
        : base(message)
        {
        }
    }

    class LeStackException: ArgumentException{
        public LeStackException(string message)
        : base(message)
        {
        }
    }

    // Text data processing class    
    class Le    
    {
        // Stack and list part

        // gets all elements but last from array like from inverted list
        public static string[] tail(string[] stack){
            string[] new_stack=new string[stack.Length-1];
            for(int i=0; i<stack.Length-1; i++){
                new_stack[i]=stack[i];
            }
            return new_stack;
        }

        // gets a last element from array as from inversed list
        public static string head(string[] stack, bool shut_up=false){  
            if(stack.Length==0){
                if(!shut_up){
                    throw new LeStackException("Stack passed to Le.head is empty");
                }
                return "";
            }else{
                return stack[stack.Length-1];
            }
        }

        // gets all but i-th element
        public static string[] but(string[] stack, int bad_index, bool shut_up=false){
            string[] new_stack=new string[stack.Length-1];
            for(int i=0; i<stack.Length-1; i++){
                if(i>=bad_index){
                    new_stack[i]=stack[i+1];
                }else{
                    new_stack[i]=stack[i];
                }
            }
            return new_stack;
        }
        
        // pops an element from a string array, as from a stack
        public static string pop(ref string[] stack, bool shut_up=false){
            if(stack.Length==0){
                if(!shut_up){
                    throw new LeStackException("Stack passed to Le.pop is empty");
                }
                return "";
            }else{
                string the_head=head(stack);
                stack=tail(stack);
                return the_head;
            }
        }

        // pushes string in a string array line in a stack
        public static string[] push(string[] stack, string last){   
            string[] new_stack=new string[stack.Length+1];
            for(int i=0; i<stack.Length; i++){
                new_stack[i]=stack[i];
            }
            new_stack[stack.Length]=last;
            return new_stack;
        }

        // swaps two elements in a string array like in a stack
        public static void swap(ref string[] stack, bool shut_up=false){
            if(stack.Length<2){
                if(!shut_up){
                    throw new LeStackException("Stack passed to Le.swap is too small. Can't swap less then two elements.");
                }
                return;
            }else{
                string temp=stack[stack.Length-1];
                stack[stack.Length-1]=stack[stack.Length-2];
                stack[stack.Length-2]=temp;
            }
        }

        // check if array contains a string
        public static bool contains(string[] stack, string sample){
            foreach(string s in stack){
                if(s==sample){
                    return true;
                }
            }
            return false;
        }

        // String[] handling part

        // just to pair split
        public static string join(string[] stack, string separator){
            return String.Join(separator, stack);
        }

        // works with both strings
        public static string[] split(string input, string separator){
            if(separator.Length==1){
                return input.Split(separator[0]);
            }
            string[] ss={};
            if(input==""){
                return ss;
            }
            int l_pos=0;
            int c_pos=input.IndexOf(separator);    
            while(c_pos!=-1){
                ss=push(ss, input.Substring(l_pos, c_pos-l_pos));
                l_pos=c_pos+separator.Length;
                c_pos=input.IndexOf(separator,c_pos+1);
            }
            ss=push(ss, input.Substring(l_pos, input.Length-l_pos));
            return ss;
        }

        // removes all the trash from input        
        public static string clear(string input, string trash){
            return join(split(input, trash), "");
        }

        // clones a string a couple of times
        public static string clone(string origin, uint count){
            if(count==0){
                return"";
            }else{
                return origin+clone(origin, count-1);
            }
        }

        // XML to Dictionary part

        // this suppose to be inner funtion for xml_to_dict
        private static void add_to_dict(string key, string value, 
                                        Dictionary<string, string> the_map, Dictionary<string, int> counter, 
                                        bool first_only){    
            if(counter.ContainsKey(key)){
                if(first_only){ // only the first element counts
                    return;
                }
                counter[key]++;
                if(counter[key]==2){
                    the_map[key+"_1"]=the_map[key];
                    the_map.Remove(key);
                }
                the_map[key+'_'+counter[key].ToString()]=clear(value, "\"");
            }else{
                counter[key]=1;
                the_map[key]=clear(value, "\"");
            }
        }        

        // converts an xml string to a dictionary
        public static Dictionary<string, string> xml_to_dict(string xml, string wanted_key="", bool first_only=false){  
            Dictionary<string, string> the_map = new Dictionary<string, string>();
            Dictionary<string, int> counter = new Dictionary<string, int>();
            string[] ls=xml.Split('<');
            string l0_prev="";
            string l1_prev="";
            string[] prefix={};
            foreach(string l in ls){
                string[] ls2=l.Split('>');
                if(ls2.Length==2){
                    if(ls2[0][0]=='!' || ls2[0][0]=='?'){ //  <?blah-blah?><!blah>
                        continue;
                    }
                    if(ls2[0].Contains(" ")){ //  <a b=1 c=2 d=3>
                        string[] pairs=ls2[0].Split(' ');
                        foreach(string pair in pairs){
                            string[] key_value=pair.Split('=');
                            if(key_value.Length==2){
                                if(wanted_key=="" || contains(prefix, wanted_key) || pairs[0]==wanted_key){ // to lessen processing time
                                    string key=join(prefix, "/")+((prefix.Length==0)?"":"/")+pairs[0]+'/'+key_value[0];
                                    add_to_dict(key, key_value[1],   the_map, counter, first_only);
                                }
                            }
                        }
                        if(ls2[0][ls2[0].Length-1]=='/'){   //  <a b=1 c=2 d=3 />
                            continue;
                        }
                        ls2[0]=pairs[0];
                    }
                    if(ls2[0]=="/"+l0_prev){    //  <a>1</a>
                        if(wanted_key=="" || contains(prefix, wanted_key)){
                            string key=String.Join("/",prefix);
                            add_to_dict(key, l1_prev,   the_map, counter, first_only);
                        }
                        prefix=tail(prefix);
                    }else{
                        if(ls2[0]!=""){
                            if(ls2[0][0]=='/'){ // </a>
                                prefix=tail(prefix);
                            }else{
                                prefix=push(prefix, ls2[0]);
                            }
                        }
                    }
                    l0_prev=ls2[0];
                    l1_prev=ls2[1];
                }
            }
            return the_map;
        }

        // Simple tree class to use with dict_to_xml
        class LeTree{
            Dictionary<string, LeTree> nodes=new Dictionary<string, LeTree>();
            string value_if_any="";

            public void AddValue(string[] key, string value){
                if(key.Length==0){
                    value_if_any=value;
                }else{
                    if(!nodes.ContainsKey(key[0])){
                        nodes[key[0]]=new LeTree();
                    }
                    nodes[key[0]].AddValue(Le.but(key, 0), value);
                }
            }

            public string GetXML(string tab="", uint tab_count=0){
                if(value_if_any!=""){
                    return value_if_any;
                }else{
                    string xml="";
                    foreach(KeyValuePair<string, LeTree> entry in nodes){
                        xml+="\n"+clone(tab,tab_count)+"<"+entry.Key+">"+entry.Value.GetXML(tab, tab_count+1)+"</"+entry.Key+">\n";
                        if(tab_count>0){
                            xml+=clone(tab,tab_count-1);
                        }
                    }
                    return xml;
                }
            }
        }

        // converts Dictionary<string, string> to a xml string
        public static string dict_to_xml(Dictionary<string,string> dict, string tab=""){
            LeTree xtree=new LeTree();
            foreach(KeyValuePair<string,string> entry in dict){
                xtree.AddValue(split(entry.Key, "/"), entry.Value);
            }
            return xtree.GetXML(tab);
        }
       
        // Save/load part

        // saves data as a file. Not in a file, but as a file name. Works with directories
        public static void save(string key, string value){ 
            string[] path = split(key,"/");
            string cur_path="";
            for(int i=0; i<path.Length-1; i++){
                cur_path+=path[i]+"/";
                if(!Directory.Exists(cur_path)){
                    Directory.CreateDirectory(cur_path);
                }
            }
            string dir = join(tail(path), "/");
            string file = head(path);

            string [] fileEntries = Directory.GetFiles(dir);
            foreach(string fileName in fileEntries){
                string pair=head(split(fileName,@"\"));
                if(pair.Split('.')[0]==file){
                    File.Delete(fileName);
                }
            }
            File.WriteAllText(key+"."+value, "");
        }

        // loads string from a file name. A pair function for Le.save
        public static string load(string key, bool shut_up=false){
            string[] path=split(key, "/");
            string dir=join(tail(path),"/");
            if(dir==""){
                dir=".";
            }
            string file=head(path);
            string [] fileEntries = Directory.GetFiles(dir);
            foreach(string fileName in fileEntries){
                string pair=head(split(fileName,@"\"));
                if(pair.Split('.')[0]==file){
                    return pair.Split('.')[1];
                }
            }
            if(!shut_up){
                throw new LeLoadException("Le.load fails, because no '"+key+"' entry found.");
            }
            return "";
        }
    }
}
